#include "bitmap.h"
#include <png.h>
#ifndef NO_PNG_STRUCT_HEADER
	#include <pngstruct.h>
#endif
#include "../core/base/file_io.h"

KBitmap::CreationDesc::CreationDesc()
{
	wantAlpha = true;
	forceGray = false;
	defaultAlpha = 0x00;
	useColorKey = false;
	colorKey = 0;
}

KBitmap::KBitmap()
{
	m_isValid = false;
}

KBitmap::KBitmap(const char* fileName)
{
	m_isValid = LoadFromFile(fileName, this, CreationDesc());
}

KBitmap::KBitmap(int width, int height, KFormat format)
{
	m_isValid = false;
	m_width = width;
	m_height = height;
	if (m_width < 1 || m_height < 1)
		return;

	m_format = format;
	if (m_format == eRGBA_8888) {
		m_bpp = 4;
	}	
	else if (m_format == eRGBA_L8) {
		m_bpp = 1;
	}
	else {
		// TODO: support more formats.
		return;
	}

	m_pData.resize(m_bpp * m_width * m_height);

	m_isValid = true;
}

KBitmap::~KBitmap()
{

}

KFormat KBitmap::GetFormat() const
{
	return m_format;
}

BYTE* KBitmap::GetPixelData()
{
	return &m_pData[0];
}

const BYTE* KBitmap::GetPixelData() const
{
	return &m_pData[0];
}

int KBitmap::Width() const
{
	return m_width;
}

int KBitmap::Height() const
{
	return m_height;
}

bool KBitmap::IsValid() const
{
	return m_isValid;
}

bool KBitmap::Allocate(int w, int h, KFormat format)
{
	m_width = w;
	m_height = h; 
	m_format = format;
	if (m_format == eRGBA_8888)
		m_bpp = 4;
	else if (m_format == eRGBA_L8) 
		m_bpp = 1;
	else {
		// TODO: support more formats.
		m_bpp = 0;
	}
	m_pData.resize(m_width*m_bpp * h);
	return true;
}

void* KBitmap::GetRowPtr(int row)
{
	return &m_pData[row*m_width*m_bpp];
}

void ReadDataFromInputStream(png_structp png_ptr, png_bytep outBytes,
							 png_size_t byteCountToRead)
{
	if((void*)png_ptr->io_ptr == NULL)
		return;   

	
	KFileReader* inputStream = (KFileReader*)png_ptr->io_ptr;
	unsigned int bytesRead = inputStream->Read(
		outBytes,
		(unsigned int)byteCountToRead);

	if((png_size_t)bytesRead != byteCountToRead)
		return;   // add custom error handling here

} 

bool KBitmap::LoadFromFile(const char* fileName, KBitmapDataHolder* pHolder, const CreationDesc& createDesc)
{
	if (!pHolder) return false;
	
	std::vector<BYTE*> pRowPointers;
	KFileReader pngFile(fileName);

	if(!pngFile.IsFileOpen())
		return false;

	BYTE header[8]; 

	//confirm png header
	pngFile.Read(header, 8);
	if (!png_check_sig(header, 8))
	{
		return false;
	}

	//create pPng and info_png
	png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!pPng)
	{
		return false;
	}

	png_infop pPngInfo = png_create_info_struct(pPng);
	if (!pPngInfo)
	{
		png_destroy_read_struct(&pPng,NULL, NULL); 
		return false;
	}
	png_set_read_fn(pPng, &pngFile, ReadDataFromInputStream);

	// it's a goto (in case png lib hits the bucket)
	/*if(setjmp(png_jmpbuf(pPng))) 
	{  
		fclose(pFile);
		png_destroy_read_struct(&pPng, &pPngInfo, NULL);
		return false;
	}*/

	//png_init_io(pPng, pngFile);
	png_set_sig_bytes(pPng, 8);  // we already read the 8 signature bytes 
	png_read_info(pPng, pPngInfo);  // read all PNG info up to image data 

	int bitDepth, colorType;
	png_uint_32 width, height;
	png_get_IHDR(pPng, pPngInfo, &width, &height, &bitDepth, &colorType,NULL, NULL, NULL);

 

	// apply filters to image so we can get proper image data format
	if (colorType == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(pPng);
	if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
		png_set_gray_to_rgb(pPng);
	if (png_get_valid(pPng, pPngInfo, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(pPng);

	png_get_IHDR(pPng, pPngInfo, &width, &height, &bitDepth, &colorType,NULL, NULL, NULL);

	bool m_bForce8BitDepth = true;
	if(bitDepth == 16)
	{
		if(m_bForce8BitDepth)
			png_set_strip_16(pPng); //make 8-bit
		else
			png_set_swap(pPng); //swap endian order (PNG is Big Endian)
	}

	if(m_bForce8BitDepth) // force 8bit depth per channel
		bitDepth = 8;

	int byteDepth = bitDepth/8;

	 // Always convert image to this format
	KFormat format;
	int destBpp;
	if (createDesc.forceGray) {
		format = eRGBA_L8;
		destBpp = 1;
	}
	else {
		format = eRGBA_8888;
		destBpp = 4;
	}
	int bytesPerPixel = 0;
	switch(colorType)
	{
	case PNG_COLOR_TYPE_RGB_ALPHA:
		bytesPerPixel = byteDepth*4;
		break;

	case PNG_COLOR_TYPE_RGB:
	default:
		bytesPerPixel = byteDepth*3;
		break;
	}

	png_read_update_info(pPng, pPngInfo);

	int rowbytes = png_get_rowbytes(pPng, pPngInfo);
	std::vector<BYTE> rowData(rowbytes);
	BYTE* pRowData = &rowData[0];
	if (pHolder->Allocate(width, height, format) &&
		pHolder->BeginEdit()) {

		for (unsigned int i = 0;  i < height;  ++i) {
			BYTE* pDestRow = (BYTE*)pHolder->GetRowPtr(height - i - 1);
			png_read_row(pPng, pRowData, NULL);
			if (format == eRGBA_8888) {

				for (unsigned int x = 0; x < width; ++x) {
					*(pDestRow + 4*x + 3) = createDesc.defaultAlpha;
					memcpy(pDestRow + destBpp*x, pRowData + bytesPerPixel*x, bytesPerPixel);
				}

			}
			else if (format == eRGBA_L8) {

				for (unsigned int x = 0; x < width; ++x) {
					BYTE r = *(pRowData + x*bytesPerPixel + 0);
					BYTE g = *(pRowData + x*bytesPerPixel + 1);
					BYTE b = *(pRowData + x*bytesPerPixel + 2);
					BYTE gray = BYTE((int(r) + int(g) + int(b)) / 3);
					*(pDestRow + x) = gray;
				}
			}
		}
	}
	pHolder->EndEdit();

	png_read_end(pPng, NULL);
	png_destroy_read_struct(&pPng, &pPngInfo, NULL);
	
	return true;
}
